import telnetlib
import time
import subprocess
import paramiko
import datetime
import os
import filecmp
from typing import Dict, List, Optional, Tuple




# 本脚本是直接断开电源下电, 而非先 power off -> 再断电

# bmc info
bmc_ip = "192.168.67.59"
bmc_name = "taobao"
bmc_passwd = "9ijn0okm"

os_ip = "192.168.67.123"
os_name = "root"
os_passwd = "1234"

client: Optional[paramiko.SSHClient] = None
ssh_client: Optional[paramiko.SSHClient] = None

# PDU info
pdu_ip = "192.168.2.200"
pdu_name = 'apc'
pdu_passwd = 'admin'
AC_num = ["1"]  # 8为apc盒子的电源口序号
loops = 100  # AC_Cycle loops

# memory
memory_commands = ['dmidecode -t 17', 'lscpu', 'cat /var/log/messages','ipmitool sel list','ipmitool sel clear']
before_memory_log = ['before_meminfo', 'before_cpuinfo', 'before_messages']
after_memory_log = ['after_memInfo', 'after_cpuinfo', 'after_messages']


#CXL_commands = ['echo 1234 | sudo -S umxc status | head -n 16']
#CXL_log = ['CXL_Status.log']

pdu_on = []
pdu_off = []

#number = 0
capacity = "2T"  ##desgin

test_log = 'testAC.log'
#preinfo = {}
#foldname = ""



def print_time(ctx: str):
    tstring = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    print(f'{tstring}, {ctx}')

def connect_os_ssh(filename, os_ip,os_username,os_password ):
    """连接到操作系统SSH"""
    try:
        ssh_client = paramiko.SSHClient()
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh_client.connect(
            hostname=os_ip,
            username=os_username,
            password=os_password,
            timeout=10
        )
        return True
    except Exception as e:
        save_file(f"连接操作系统SSH失败: {str(e)}",True)
        return False

def disconnect_ssh(filename):
    """断开所有连接"""
    if ssh_client:
        ssh_client.close()
        ssh_client = None
        save_file(filename,f"disconnect os ssh",True)



# 远程连接 Linux OS
def log_os(os_ip, os_name, os_passwd):
    # 建立连接
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    #client.connect(hostname=os_ip, port=22, username=os_name, password=os_passwd)
    client.connect(
        hostname=os_ip,
        username=os_name,
        password=os_passwd,
        timeout=10
    )


    # 执行命令并返回结果
    #stdin, stdout, stderr = client.exec_command(f'ipmitool -I lanplus -H {bmc_ip} -U {bmc_name} -P {bmc_passwd} user list')
    stdin, stdout, stderr = client.exec_command(
        f'ipmitool -I lanplus -H {bmc_ip} -U {bmc_name} -P {bmc_passwd} poweron')

    strs = stdout.read().decode('utf-8')
    print_time(strs)
    return client

def close_os_ssh():
    if(client):
        client.close()
        #client = None
        save_file(filename, f"disconnect os ssh", True)
    return


# 远程登录BMC
# def log_bmc(bmc_ip, bmc_name, bmc_pass):

#     client = paramiko.SSHClient()
#     client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
#     client.connect(hostname=bmc_ip, port=22, username=bmc_name, password=bmc_pass)
#     # 执行命令并返回结果
#     stdin, stdout, stderr = client.exec_command('ipmitool user list')
#     strs = stdout.read().decode('utf-8')
#     print(strs)
#     return client


# 在指定文件夹下保存log
def save_file(filename, lines, is_print):
    #path = f"{filename}\\{test_log}"
    #print_time(path)
    f1 = open(filename, 'a')
    tstring = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    if lines != "\n":
        f1.write(f"{tstring},{lines}" + "\n")  #f1.write(lines + "\n")
    f1.flush()
    f1.close()
    if is_print:
        print_time(lines)


# 测试前后获取内存，CPU信息
def get_info(commands, filenames, is_print):
    # 远程连接
    client = log_os(os_ip, os_name, os_passwd)
    for i in range(len(commands)):
        stdin, stdout, stderr = client.exec_command(commands[i])
        strs = stdout.read().decode('utf-8')
        path = f"{foldname}\\{filenames[i]}"  #path = foldname + f"\{filenames[i]}"
        file = open(path, mode='w')
        file.write(strs)
        if is_print:
            print(strs)
    return True


# # 获取硬盘数量
# def count_disk():
#     client = log_os(os_ip, os_name, os_passwd)
#     stdin, stdout, stderr = client.exec_command("fdisk -l | grep \"/dev/\" | awk '{print($2)}' | awk -F \":\" '{"
#                                                 "print($1)}' ")
#     strs = stdout.read().decode('utf-8').split('\n')
#     for k in range(len(strs)):
#         data_sas_sata = [i for i in strs if '/dev/sd' in i]
#     for k in range(len(strs)):
#         data_nvme = [i for i in strs if '/dev/nvme' in i]
#     disk_number = len(data_sas_sata) + len(data_nvme)
#     return disk_number


# 连接APC盒子
def run_telnet(filename, host, user, passwd):
    try:
        tn = telnetlib.Telnet(host)
        tn.read_until(b'User Name : ', timeout=5)
        tn.write(user.encode() + b'\r\n')

        tn.read_until(b'Password  : ', timeout=5)
        tn.write(passwd.encode() + b'\r\n')

        index, obj, output = tn.expect([b'>'], timeout=5)
        result = output.decode('utf-8')
        # tn.read_until(b'>')
        # print(result)

        save_file(filename, 'connect APC successfully...', True)
        return tn
    except Exception as e:
        result = '连接失败{}'.format(e)
        save_file(filename, result, True)


def wait_sut_online_1st(ip):  # curr_cycle,cycles,
    timeout = 800
    time_start = time.time()
    while True:
        time1 = time.time()
        if (time1 - time_start > timeout):
            print_time(f"ping {ip},timeout over {timeout} sec.")
            return False

        i = 0
        p = subprocess.Popen("ping.exe -n 15 {}".format(ip), stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                             # stderr=subprocess.PIPE, shell=True)
                             # p = subprocess.Popen(["ping.exe", ip], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE, shell=True)
        out = p.stdout.read().decode('gbk')
        # self.logger.info(out)
        for line in out.splitlines():
            if "来自 " + str(ip) + " 的回复: 字节" in line:
                i += 1
        if i == 15:
            print_time(f"ping {ip} passed")
            time.sleep(1)
            break
        else:
            # self.logger.info(f"ping {ip} failed")
            print_time(f"ping {ip}, failed ...")
            time.sleep(3)

    return True


def wait_sut_online(ip):   #curr_cycle,cycles,
    timeout = 500
    time_start = time.time()
    while True:
        time1 = time.time()
        if(time1-time_start > timeout):
            print_time(f"ping {ip},timeout over the {timeout} sec. should be goto next AC cycle")
            return False
    
        i = 0
        p = subprocess.Popen("ping.exe -n 15 {}".format(ip), stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                             # stderr=subprocess.PIPE, shell=True)
                             # p = subprocess.Popen(["ping.exe", ip], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE, shell=True)
        out = p.stdout.read().decode('gbk')
        # self.logger.info(out)
        for line in out.splitlines():
            if "来自 " + str(ip) + " 的回复: 字节" in line:
                i += 1
        if i == 15:
            print_time(f"ping {ip} passed")
            time.sleep(1)
            break
        else:
            # self.logger.info(f"ping24 {ip} failed")
            print_time(f"ping {ip}, failed ...")
            time.sleep(2)
    return True


def wait_bmc_online(ip):  # curr_cycle,cycles,
    timeout = 500
    time_start = datetime.datetime.now()

    while True:
        time_end = datetime.datetime.now()
        diff = time_end - time_start
        if (diff.total_seconds() > timeout):
            print_time(f"ping {ip},timeout over the {timeout} sec. should be goto next AC cycle")
            return False

        i = 0
        p = subprocess.Popen("ping.exe -n 15 {}".format(ip), stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                             # stderr=subprocess.PIPE, shell=True)
                             # p = subprocess.Popen(["ping.exe", ip], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE, shell=True)
        out = p.stdout.read().decode('gbk')
        # self.logger.info(out)
        for line in out.splitlines():
            if "来自 " + str(ip) + " 的回复: 字节" in line:
                i += 1
        if i == 15:
            print_time(f"ping bmc {ip} passed")

            # p = subprocess.Popen(f"ipmitool -I lanplus -H {bmc_ip}-U {bmc_name} -P {bmc_passwd} power on",
            #                      #stdin=subprocess.PIPE, stdout=subprocess.PIPE,
            #                      stderr=subprocess.PIPE, shell=True)
            # out = p.stdout.read().decode('gbk')
            # print_time(f"start os {out}")
            #time.sleep(1)

            return True
        else:
            # self.logger.info(f"ping24 {ip} failed")
            print_time(f"ping {ip}, failed ...")
            time.sleep(2)
            continue

    return True

def wait_os_online(ip):  # curr_cycle,cycles,
    timeout = 500
    time_start = datetime.datetime.now()

    while True:
        time_end = datetime.datetime.now()
        diff = time_end - time_start
        if (diff.total_seconds() > timeout):
            print_time(f"ping {ip},timeout over the {timeout} sec. should be goto next AC cycle")
            return False

        i = 0
        p = subprocess.Popen("ping.exe -n 15 {}".format(ip), stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                             # stderr=subprocess.PIPE, shell=True)
                             # p = subprocess.Popen(["ping.exe", ip], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE, shell=True)
        out = p.stdout.read().decode('gbk')
        # self.logger.info(out)
        for line in out.splitlines():
            if "来自 " + str(ip) + " 的回复: 字节" in line:
                i += 1
        if i == 15:
            print_time(f"ping {ip} passed")
            time.sleep(1)
            return True
        else:
            # self.logger.info(f"ping24 {ip} failed")
            print_time(f"ping {ip}, failed ...")
            time.sleep(2)
            continue

    return True


def wait_sut_offline(ip):
    # time.sleep(5)
    while True:
        i = 0
        # p = subprocess.Popen(["ping.exe", ip], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
        # stderr=subprocess.PIPE, shell=True)
        # 调用cmd  ping 服务器IP,如果在命令行中一直出现请求超时则,i+1,一直ping
        p = subprocess.Popen("ping.exe -n 1 {}".format(ip), stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE, shell=True)
        out = p.stdout.read().decode('gbk')
        # self.logger.info(out)
        for line in out.splitlines():
            # if "来自 " + str(ip) + " 的回复: 字节" in line:
            # if "来自 " + "10.0.0.1" + " 的回复:" in line:
            # print('测试：', line)
            if "TTL 传输中过期。" in line or "请求超时。" in line:
                i += 1
        if i == 1:
            # self.logger.info(f"ping {ip} failed")
            break
        else:
            # self.logger.info(f"ping {ip} passed")
            time.sleep(5)
    return True


def power_pdu(bmc_ip, os_ip, filename):
    # power Off SUT
    tn1 = run_telnet(filename, pdu_ip, pdu_name, pdu_passwd)
    for i in pdu_off:
        tn1.write(i.encode() + b'\r\n')
        time.sleep(1)

    result = tn1.read_very_eager().decode('utf-8')
    # time.sleep(5)

    if 'E000: Success' in result:
        save_file(filename, 'wait bmc offline...', True)
    # time.sleep(5)

    bmc_offline = wait_sut_offline(bmc_ip)  # ping 一下看看bmc是否已经下线
    if bmc_offline:
        save_file(filename, 'SUT is offline...', True)

    # power on SUT
    tn2 = run_telnet(filename, pdu_ip, pdu_name, pdu_passwd)
    for j in pdu_on:
        tn2.write(j.encode() + b'\r\n')
        time.sleep(1)
        res = tn2.read_very_eager().decode('utf-8')
        if 'E000: Success' in res:
            save_file(filename, 'wait to bmc online...', True)

    bmc_online = wait_bmc_online(bmc_ip) #wait_sut_online
    if bmc_online:
        save_file(filename, 'bmc is online,wait to boot os...', True)
    # time.sleep(5)

    p = subprocess.Popen(f"ipmitool -I lanplus -H {bmc_ip} -U {bmc_name} -P {bmc_passwd} power on",
                         stderr=subprocess.PIPE, shell=True)


    os_online = wait_os_online(os_ip) #wait_sut_online
    if os_online:
        save_file(filename, "succeed to boot in os", True)
    return os_online

def execute_command(logname, command):
    ssh = log_os(os_ip, os_name, os_passwd)
    stdin, stdout, stderr = ssh.exec_command(command)
    result = stdout.read().decode('utf-8')

    save_file(logname, command, False)
    save_file(logname, result, True)




def pre_accycle(filename):
    # 1st
    # power Off SUT
    tn1 = run_telnet(filename, pdu_ip, pdu_name, pdu_passwd)
    for i in pdu_off:
        tn1.write(i.encode() + b'\r\n')
        time.sleep(1)

    result = tn1.read_very_eager().decode('utf-8')
    # time.sleep(5)

    if 'E000: Success' in result:
        save_file(filename, 'APC offline Success ...', True)

    time.sleep(1)

    tn2 = run_telnet(filename, pdu_ip, pdu_name, pdu_passwd)
    for j in pdu_on:
        tn2.write(j.encode() + b'\r\n')
        time.sleep(1)
        res = tn2.read_very_eager().decode('utf-8')
        if 'E000: Success' in res:
            save_file(filename, 'APC online Success...', True)


   #wait_sut_online_1
    ret = wait_bmc_online(bmc_ip)
    if ret:
        save_file(filename, "connect bmc ipAddr successfully...", True)
        p = subprocess.Popen(f"ipmitool -I lanplus -H {bmc_ip} -U {bmc_name} -P {bmc_passwd} power on",
                             stderr=subprocess.PIPE, shell=True)

    else:
        save_file(filename, f"Error: failed to connect {bmc_ip},Please check bmc ipaddr..... ", True)
        #exit()



    # if wait_os_online(os_ip):
    #     save_file(filename, "succeed to boot in os", True)
    # else:
    #     save_file(filename, f"Error: failed to connect os ip, {os_ip},Please check os ipaddr..... ", True)

    # #测试前清除dmesg 日志
    # client_os = log_os(os_ip, os_name, os_passwd)
    # client_os.exec_command('dmesg -C')
    # client_os.exec_command('ipmitool sel clear')
    #
    # stdin1, stdout1, stderr1 = client_os.exec_command('ipmitool sel list')
    # sel_list_info = stdout1.read().decode('utf-8').strip()  # 将返回的字符串后面隐藏的 /n 清除掉，否则会影响判断
    # save_file(filename, f"1st boot ipmi sel list info: {sel_list_info}", True)
    # close_os_ssh()





    # stdin1, stdout1, stderr1 = client_os.exec_command('echo $?')
    # flag = stdout1.read().decode('utf-8').strip()  # 将返回的字符串后面隐藏的 /n 清除掉，否则会影响判断
    # if flag == "0":
    #     save_file(filename, 'clearing os sel log successfully...', True)
    # else:
    #     save_file(filename, 'failed to clear os log...', True)

    # 记录硬盘个数, 内存容量
    #number = count_disk()
    #stdin2, stdout2, stderr2 = client_os.exec_command('lsmem | grep -i \'Total online memory\' | awk \'{print $4}\'')
    #capacity = stdout2.read().decode('utf-8')

    #preinfo = {'number': number, 'capacity': capacity}

    # stdin3, stdout3, stderr3 = client_os.exec_command('lscpu')
    # cpuinfo = stdout3.read().decode('utf-8')
    # save_file('pre_info.log', f"Disk number is {number} \nMemory capacity is {capacity} \nCPU info is: \n {cpuinfo}",
    #           True)

    # 测试前获取内存dmidecode信息
    #get_info(memory_commands, before_memory_log, False)
    return


def ac_cycle(loops ,filename :str):
    save_file(filename, '***** Test Start *****', True)
    for loop in range(loops):
        tim = datetime.datetime.now()
        tim = tim.strftime("%Y-%m-%d %H:%M:%S")
        save_file(filename, f'*****{tim}，loops:{loop + 1}/{loops}*****', True)

        # stdin1, stdout1, stderr1 = client_os.exec_command('echo $?')
        # flag = stdout1.read().decode('utf-8').strip()  # 将返回的字符串后面隐藏的 /n 清除掉，否则会影响判断
        # if flag == "0":
        #     save_file(filename, 'clearing bmc sel log successfully...', True)
        # else:
        #     save_file(filename, 'failed to clear bmc log...', True)


        # 一轮上下电
        os_line_ret = power_pdu(bmc_ip, os_ip, filename)
        if(os_line_ret==False):
            save_file(filename, f'Error: failed to wait os online', True)
            continue

        client_os = log_os(os_ip, os_name, os_passwd)

        # 记录内存容量
        ssh = log_os(os_ip, os_name, os_passwd)  # 上下电后，前面的client已经失效了，需重新连接
        stdin2, stdout2, stderr2 = ssh.exec_command('lsmem | grep -i \'Total online memory\' | awk \'{print $4}\'')
        current_capacity = stdout2.read().decode('utf-8')
        compareRet = (current_capacity.encode('utf-8').strip() == capacity.encode('utf-8').strip())
        if compareRet:
            save_file(filename, f"Memory capacity check OK，design={capacity},current capacity={current_capacity}", True)
        else:
            save_file(filename, f"Error: Memory capacity check Failed，design={capacity},current capacity={current_capacity}", True)


        # 上下电后收集OS dmesg 报错信息
        save_file(filename, 'OS dmesg info: ', True)
        stdin1, stdout1, stderr1 = ssh.exec_command('dmesg -T | grep -i edac')
        dmesginfo1 = stdout1.read().decode('utf-8').strip()  # 将返回的字符串后面隐藏的 /n 清除掉，否则会影响判断
        save_file(filename, dmesginfo1, False)

        stdin2, stdout2, stderr2 = ssh.exec_command('dmesg -T | egrep -i \"error | fail | warn | wrong | bug | respond | pending\"')
        dmesginfo2 = stdout2.read().decode('utf-8').strip()  # 将返回的字符串后面隐藏的 /n 清除掉，否则会影响判断
        save_file(filename, dmesginfo2, False)


        # 记录上电后 bmc sel日志
        stdin3, stdout3, stderr3 = ssh.exec_command('ipmitool sel list | grep Memory DIMM')
        sel_list_msg = stdout3.read().decode('utf-8').strip()
        save_file(filename, f'bmc sel info:=========\n {sel_list_msg}', True)


        # 收集测试过程中 内存的信息(每轮)
        stdin4, stdout4, stderr4 = ssh.exec_command(f'dmidecode -t 17')
        memoryinfo = stdout4.read().decode('utf-8')
        save_file(filename, f'bmc sel info:=========\n {memoryinfo}', False)

        #save_file(filename, 'after test AC. should be clear bmc sel log.....', True)
        execute_command(filename, "ipmitool sel clear")
        save_file(filename, f'*****datetime: {tim} loops: {loop + 1}/{loops}  test Over*****', True)
        close_os_ssh()

    return

if __name__ == "__main__":
    print_time(f"start ===")
    #  拼接上下电命令
    for k in range(len(AC_num)):
        pdu_off.append('olDlyOff ' + AC_num[k])
        pdu_on.append('olDlyOn ' + AC_num[k])

    # for key in list(pdu_off):
    #     print(f'pdu_off key={key}')
    #
    # for key in list(pdu_on):
    #     print(f'pdu_on key={key}')

    time1 = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    foldname = f"ACTest_log"

    currdir = os.getcwd()
    fullpath = f"{currdir}\\{foldname}"
    #print(fullpath)

    if(os.path.exists(fullpath) == False):
        os.mkdir(fullpath)
        #print(f"{fullpath} dir donot exist. will create dir")

    #filename = f"AC_{time1}.log"

    filename = f"{fullpath}\\AC_{time1}.log"

    #preinfo = pre_accycle('pre_test.log')
    #number = preinfo.get('number')
    #capacity = preinfo.get('capacity')
    #run_telnet(pdu_ip, pdu_name, pdu_passwd)




    pre_accycle(filename)

    ac_cycle(loops, filename)